package com.minelx.woods.task.common;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;

public class Node<E> {
	private final E element;

	private final List<Node<E>> children;

	public Node(E element, List<Node<E>> children) {
		this.element = element;
		this.children = children;
	}

	public Node<E> find(Predicate<Node<E>> predicate) {
		return allNodes().stream()
				.filter(predicate)
				.findFirst().orElseThrow(() -> new RuntimeException("can't find element."));
	}

	public List<Node<E>> children() {
		return children;
	}

	public E element() {
		return element;
	}

	public boolean isLeaf() {
		return children().isEmpty();
	}

	public List<Node<E>> allNodes() {
		List<Node<E>> result = new LinkedList<>();
		result.add(this);
		// add children
		children.stream().map(Node::allNodes)
				.flatMap(Collection::stream)
				.forEach(result::add);
		return result;
	}

	public List<E> leaves() {
		return leafNodes().stream().map(Node::element).collect(toList());
	}

	public List<Node<E>> leafNodes() {
		if (isLeaf()) {
			return singletonList(this);
		}
		return children.stream().map(Node::leafNodes)
				.flatMap(Collection::stream)
				.collect(toList());
	}

	public Stream<Node<E>> subs() {
		Stream.Builder<Node<E>> result = Stream.builder();
		forEachSub(result::add);
		return result.build();
	}

	private void forEachSub(Consumer<Node<E>> consumeSubNode) {
		if (isLeaf()) {
			// it is a leaf
			return;
		}

		// merge all children first, from bottom to top.
		children().forEach(sub -> sub.forEachSub(consumeSubNode));

		// merge childFiles already completed
		consumeSubNode.accept(this);
	}

	@Override
	public String toString() {
		return "Node{" +
				"element=" + element +
				", children=" + children +
				'}';
	}

	public static <E> Node<E> leaf(E element) {
		return new Node<>(element, emptyList());
	}

	public static <E> Node<E> create(E current, Function<E, List<E>> divider, Predicate<E> isLeaf) {
		if (isLeaf.test(current)) {
			return leaf(current);
		}
		// multi nodes
		List<Node<E>> childTasks = divider.apply(current).stream()
				.map(child -> create(child, divider, isLeaf))
				.collect(toList());
		return new Node<>(current, childTasks);
	}
}
